﻿using System;
using System.Drawing;

namespace ru.yourpictures.Core.Imaging.Filters.PhotoFilters
{
    /// <summary>
    /// Hue, Saturation, Lightness filter.
    /// Taken from: http://www.codeproject.com/KB/vb/HSL_Filter.aspx
    /// </summary>
    [FilterImplementation(GdiPlusImplemented = false, LowLevelImplemented = true)]
    public class HSLFilter : FilterBase
    {
        private double _hue = 0.0;
        private double _saturation = 0.0;
        private double _lightness = 0.0;

        #region Public properties
        public double Lightness
        {
            get { return _lightness; }
            set { _lightness = value; }
        }

        /// <summary>
        /// Get or set saturation correction value.
        /// </summary>
        /// <value>Double in range [-100..+100]%.</value>
        public double Saturation
        {
            get { return _saturation; }
            set { _saturation = value; }
        }

        /// <summary>
        /// Get or set hue correction value.
        /// </summary>
        /// <value>Any double, will be scaled to range [0..360]</value>
        public double Hue
        {
            get { return _hue; }
            set 
            { 
                _hue = value;
                while (_hue > 360.0) _hue -= 360.0;
                while (_hue < 0.0) _hue += 360.0;
            }
        }
        #endregion

        #region Ctor
        public HSLFilter(double hue, double saturation, double lightness)
        {
            _hue = hue;
            _saturation = saturation;
            _lightness = lightness;
            ValidateArgs();
        }
        public HSLFilter() { }
        #endregion

        #region ValidateArgs
        private void ValidateArgs()
        {
            while (_hue > 360.0) _hue -= 360.0;
            while (_hue < 0.0) _hue += 360.0;
            if (_lightness < -100 || _lightness > 100)
                throw new ArgumentException("Lightness value must be between -100 and 100.");
            if (_saturation < -100 || _saturation > 100)
                throw new ArgumentException("Saturation value must be between -100 and 100.");
        }
        #endregion

        #region FilterImplementation
        protected override void FilterImplementation()
        {
            ValidateArgs();
            ValidatePixelFormat(_b.PixelFormat);

            const double c1o60 = 1.0 / 60.0;
            const double c1o255 = 1.0 / 255.0;
            int index = 0;
            double r, g, b;
            double h, s, l, h1;
            double min, max, dif, sum;
            double f1, f2;
            double v1, v2, v3;
            double sat = 127.0 * _saturation / 100.0;
            double lum = 127.0 * _lightness / 100.0;

            for (int y = 0; y < _height; y++)
            {
                index = (y * _stride);
                for (int x = 0; x < _width; x++)
                {
                    r = _pixels[index + 2];
                    g = _pixels[index + 1];
                    b = _pixels[index];

                    #region Conversion to HSL space
                    min = r;
                    if (g < min) min = g;
                    if (b < min) min = b;

                    max = r;
                    f1 = 0.0;
                    f2 = g - b;

                    if (g > max)
                    {
                        max = g;
                        f1 = 120.0;
                        f2 = b - r;
                    }

                    if (b > max)
                    {
                        max = b;
                        f1 = 240.0;
                        f2 = r - g;
                    }

                    dif = max - min;
                    sum = max + min;
                    l = 0.5 * sum;

                    if (dif == 0)
                    {
                        h = 0.0;
                        s = 0.0;
                    }
                    else
                    {
                        if (l < 127.5)
                            s = 255.0 * dif / sum;
                        else
                            s = 255.0 * dif / (510.0 - sum);

                       h = (f1 + 60.0 * f2 / dif);
                       if (h < 0.0) h += 360.0;
                       if (h >= 360.0) h -= 360.0;
                    }
                    #endregion

                    #region Apply transformation
                    h += _hue;
                    if (h > 360.0) h -= 360.0;

                    s += sat;
                    if (s < 0.0) s = 0;
                    if (s > 255.0) s = 255.0;

                    l += lum;
                    if (l < 0.0) l = 0;
                    if (l > 255.0) l = 255.0;
                    #endregion

                    #region Conversion to RGB
                    if (s == 0)
                    {
                        r = g = b = l;
                    }
                    else
                    {
                        if (l < 127.5)
                            v2 = c1o255 * l * (255 + s);
                        else
                            v2 = l + s - c1o255 * s * l;

                        v1 = 2 * l - v2;
                        v3 = v2 - v1;
                        h1 = h + 120.0;
                        if (h1 >= 360.0) h1 -= 360.0;

                        if (h1 < 60.0) r = v1 + v3 * h1 * c1o60;
                        else if (h1 < 180.0) r = v2;
                        else if (h1 < 240.0) r = v1 + v3 * (4 - h1 * c1o60);
                        else r = v1;

                        h1 = h;
                        if (h1 < 60.0) g = v1 + v3 * h1 * c1o60;
                        else if (h1 < 180.0) g = v2;
                        else if (h1 < 240.0) g = v1 + v3 * (4 - h1 * c1o60);
                        else g = v1;

                        h1 = h - 120.0;
                        if (h1 < 0.0) h1 += 360.0;
                        if (h1 < 60.0) b = v1 + v3 * h1 * c1o60;
                        else if (h1 < 180.0) b = v2;
                        else if (h1 < 240.0) b = v1 + v3 * (4 - h1 * c1o60);
                        else b = v1;
                    }
                    #endregion

                    _pixels[index + 2] = (byte)r;
                    _pixels[index + 1] = (byte)g;
                    _pixels[index] = (byte)b;

                    index += _bytesPerPixel;
                }
            }

            throw new NotImplementedException();
        }
        #endregion

        #region Public Filter Methods
        public override void Apply(Bitmap b)
        {
            _b = b;
            _width = b.Width;
            _height = b.Height;
            throw new NotImplementedException("GDI+ mode is not implemented");
        }

        public override Bitmap Convert(Bitmap b)
        {
            Bitmap b2 = b.Clone() as Bitmap;
            Apply(b2);
            return b2;
        }
        #endregion
    }
}
